package org.seckill.service.impl;

import org.apache.commons.collections.MapUtils;
import org.seckill.dao.ISeckillDao;
import org.seckill.dao.ISuccessKilledDao;
import org.seckill.dao.cache.RedisDao;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStatEnum;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
import org.seckill.service.SeckillService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by acer on 2017/6/12.
 */
// @Component(统称组件的实例)
//@Sservice(如果知道是service就用这个注解)
//@Dao和Controller同理
@Service
public class SeckillServiceImpl implements SeckillService {

    //注入Service依赖
    @Autowired//@Resource,@Inject
    private ISeckillDao seckillDao;
    @Autowired
    private ISuccessKilledDao successKilledDao;
    @Autowired
    private RedisDao redisDao;
    private static final String satl = "klauwe&^$^%#7654765";

    //查询全部秒杀记录
    public List<Seckill> getList() {
        return seckillDao.queryAll(0, 4);
    }

    //查询单个秒杀记录
    public Seckill getById(long seckillId) {
        return seckillDao.queryById(seckillId);
    }

    //秒杀开启时返回秒杀接口
    //秒杀未开启时返回系统时间和秒杀时间
    public Exposer exportSeckillUrl(long seckillId) {
        //优化点：缓存优化
        /**
         * get from cache
         * if null
         * get ab
         * else
         *      put cache
         * locgoin
         */
        //得到秒杀对象,加一层缓存
        Seckill seckill = redisDao.getSeckill(seckillId);
        if (seckill == null) {
            seckill = getById(seckillId);
            //无对象则抛出异常
            if (seckill == null) {
                return new Exposer(false, seckillId);
            } else {
                redisDao.putSeckill(seckill);
            }
        }

        //拿到秒杀对象产品的开始时间
        Date start = seckill.getStartTime();
        //拿到结束时间
        Date end = seckill.getEndTime();
        //服务器时间
        Date now = new Date();
        //如果不在秒杀时间段内，则抛出异常
        if (start.getTime() > now.getTime() || end.getTime() < now.getTime()) {
            return new Exposer(false, seckillId, start.getTime(), end.getTime());
        }
        String md5 = getmd5(seckillId);
        //允许秒杀，返回秒杀页面接口
        return new Exposer(true, md5, seckillId);
    }

    private String getmd5(long seckillId) {
        String base = seckillId + "/" + satl;
        String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
        return md5;
    }

    //执行秒杀操作

    /**
     * 使用注解控制事务方法的优点
     * 1.开发团队达成一致,明确标注事务方法的编程风格；
     * 2.保证事务方法的时间尽可能短，不要穿插其他的网络操作（redis/http）,或者剥离到事务方法外部
     * 3.不是所有的方法都需要事务，如只要一条修改操作，只读操作不需要事务控制。
     */
    @Transactional
    public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
            throws RepeatKillException, SeckillCloseException, SeckillException {
        if (md5 == null || !getmd5(seckillId).equals(md5)) {
            //用户变更
            throw new SeckillException("seckill data rewrite");
        }
        try {
            //记录购买行为，如果发生主键冲突就是重复秒杀
            int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
            System.out.println("insertCount = " + insertCount);
            if (insertCount <= 0) {
                //重复秒杀
                throw new RepeatKillException("seckill repeated");
            } else {
                //执行秒杀逻辑
                //热点商品竞争
                int updateCount = seckillDao.reduceNumber(seckillId, new Date());
                if (updateCount <= 0) {
                    //没有更新到记录，秒杀结束
                    throw new SeckillCloseException("seckill is closed");
                } else {
                    //秒杀成功
                    SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
                    return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled);
                }
            }
        } catch (SeckillCloseException e1) {
            throw e1;
        } catch (RepeatKillException e2) {
            throw e2;
        } catch (Exception e) {
            throw new SeckillException("Seckill inner error:" + e.getMessage());
        }
    }

    @Override
    public SeckillExecution executeSeckillPro(long seckillId, long userPhone, String md5) {
        if (md5 == null || !getmd5(seckillId).equals(md5)) {
            //用户变更
            throw new SeckillException("seckill data rewrite");
        }
        Map<String, Object> map = new HashMap<String, Object>();
        Date KillTime = new Date();
        map.put("seckillId", seckillId);
        map.put("userPhone", userPhone);
        map.put("killTime", KillTime);
        map.put("result", null);
        //存储过程执行完之后,result被赋值
        try {
            seckillDao.killByProcedure(map);
            int result = MapUtils.getInteger(map, "result", -2);
            if (result == 1) {
                SuccessKilled sk = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
                return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, sk);
            } else {
                return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result));
            }
        } catch (Exception e) {
            e.printStackTrace();
            return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
        }
    }


}
